function assert(condition, message) {
    if (!condition) {
        throw message || "Assertion failed";
    }
}

// Vector is an object with fields 'v' for values
// and field length for number of elements
var vector = {};

function vectorAdd(v1, v2) {
    assert(v1.length === v2.length);
    var res = [];
    for (var i = 0; i < v1.length; i++) {
        res[i] = v1[i] + v2[i];
    }
    return res;
}

function dotProduct(v1, v2) {
    assert(v1.length === v2.length);
    var res = 0.0;
    for (var i = 0; i < v1.length; i++) {
        res += v1[i] * v2[i];
    }
    return res;
}

function scalarProduct(c, v1) {
    var res = new Array();
    for (var i = 0; i < v1.length; i++) {
        res[i] = c * v1[i];
    }
    return res;
}

function matrixMultiply(m1, m2) {
    assert(m1[0].length === m2.length);
    var mres = [];
    for (var i = 0; i < m1.length; i++) {
        var row = [];
        for (var j = 0; j < m2[0].length; j++) {
            var acc = 0;
            for (var k = 0; k < m1[i].length; k++) {
                acc += m1[i][k] * m2[k][j];
            }
            row[j] = acc;
        }
        mres[i] = row;
    }
    return mres;
}

function vectorMultiply(m, v) {
    assert(v.length === m[0].length);
    var res = [];
    for (var i = 0; i < m.length; i++) {
        var sum = 0.0;
        for (var j = 0; j < m[0].length; j++) {
            sum += m[i][j] * v[j];
        }
        res[i] = sum;
    }
    return res;
}

function transpose(m) {
    var res = [];
    for (var i = 0; i < m[0].length; i++) {
        res[i] = [];
        for (var j = 0; j < m.length; j++) {
            res[i][j] = m[j][i];
        }
    }
    return res;
}

function rotationX(angle) {
    return [[1, 0, 0], 
            [0, Math.cos(angle), - Math.sin(angle)], 
            [0, Math.sin(angle), Math.cos(angle)]];
}

function rotationZ(angle) {
    return [[Math.cos(angle), - Math.sin(angle), 0],
            [Math.sin(angle), Math.cos(angle), 0],
            [0, 0, 1]];
}

// 3D Matrix with translation:
// matrix and vector
function affine(m, v) {
    var a = { matrix : m, trans : v };
    assert(a.matrix.length === v.length);
    return a;
}

function affineTransform(a, v) {
    assert(v.length === a.matrix[0].length);
    return vectorAdd(vectorMultiply(a.matrix, v), a.trans);
}

function transformDir(a, v) {
    assert(v.length === a.matrix[0].length);
    return vectorMultiply(a.matrix, v);
}

function affineCompose(a1, a2) {
    var newm = matrixMultiply(a1.matrix, a2.matrix);
    var newv = vectorAdd(vectorMultiply(a1.matrix, a2.trans), a1.trans);
    return affine(newm, newv);
}

// Tests
function assertArrayEq(a1, a2) {
    assert(a1.length === a2.length);
    for (var i = 0; i < a1.length; i++) {
        assert(a1[i] === a2[i]);
    }
}

function assertVecEq(v1, v2) {
    assertArrayEq(v1, v2);
}

function assertMatEq(m1, m2) {
    assert(m1.length === m2.length);
    for (var i = 0; i < m1.length; i++) {
        assertArrayEq(m1[i], m2[i]);
    }
}

function assertAffEq(a1, a2) {
    assertMatEq(a1.matrix, a2.matrix);
    assertVecEq(a1.trans, a2.trans);
}

function testTranspose() {
    var m1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
    var m2 = [[1, 4, 7], [2, 5, 8], [3, 6, 9]];
    var t = transpose(m1);
    assertMatEq(t, m2);
}

function testAffineTransform() {
    var m = [[1, 2, 3], [4, 5, 6]];
    var aff = affine(m, [2, 3]);
    var t = affineTransform(aff, [4, 5, 6]);
    assertVecEq(t, [34, 80]);
}

function testAffineTransformCompose() {
    var m1 = [[1, 0, -1], [-1, 0, 1], [0, 1, -1]];
    var aff1 = affine(m1, [-1, -2, -3]);

    var m2 = [[1, 2, 3], [4, 5, 6]];
    var aff2 = affine(m2, [2, 3]);

    var m3 = [[-1, 3, -2], [-1, 6, -5]];
    var aff3 = affine(m3, [-12, -29]);

    assertAffEq(affineCompose(aff2, aff1), aff3);
}

function myTest() {
    testTranspose();
    testAffineTransform();
    testAffineTransformCompose();
}


